package com.panopset.java;

import com.panopset.Util;
import com.panopset.UtilIO;

import java.io.File;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.List;

import com.panopset.Strings;

/**
 * Format source pre-compile time.
 *
 * <p>
 * For each java source line:
 * </p>
 *
 * <ul>
 * <li>
 * Replace tab characters with four spaces.</li>
 * <li>
 * Trim spaces from the end of the lines.</li>
 * <li>
 * Trim a space after a non-empty opening bracket. This makes up for the default
 * Eclipse formatting not matching default checkstyle requirements.</li>
 * </ul>
 *
 * <p>
 * <b>com.panopset.java.UniversalVersion</b> is also updated with the current
 * date.
 * </p>
 *
 * <h5>Ant Task</h5>
 *
 * I like to put an ant build.xml file in all my maven projects that does
 * a FormatSource on my src/main/java and src/test/java directories.
 *
 * If you have the flywheel source on your system, your path could be
 * <b>../flywheel/target/classes</b> instead of the flywheel jar.  Or you could
 * specify
 * ${home}/.m2/repository/com/panopset/flywheel/&lt;version&gt;/
 * flywheel-&lt;version&gt;.jar in a maven development environment.
 *
 * <pre>
 *
 * &lt;java classname="com.panopset.java.FormatSource"
 *    classpath="&lt;lib path&gt;panopset.jar" fork="true"&gt;
 *    &lt;arg value="&lt;java source path&gt;"/&gt;
 * &lt;/java&gt;
 *
 * </pre>
 *
 * @see com.panopset.java.Version
 *
 * @author Karl Dinwiddie
 *
 */
public final class FormatSource {

    /**
     * Format Java source files.
     *
     * @param sourceFileDirectory
     *            Source directory to format.
     */
    public static void formatSource(final String sourceFileDirectory) {
        final File file = new File(sourceFileDirectory);
        Util.dspmsg("Formatting " + Util.getCanonicalPath(file));
        new FormatSource().format(file);
    }

    /**
     * FormatSource constructor.
     */
    private FormatSource() {
    }

    /**
     * args[0] = Java source code directory.
     *
     * @param args
     *            args[0] is the source root directory that you wish to format.
     */
    public static void main(final String[] args) {
        if (args == null) {
            return;
        }
        if (args.length != 1) {
            return;
        }
        File dir = new File(args[0]);
        if (!dir.exists()) {
            return;
        }
        if (!dir.isDirectory()) {
            return;
        }
        new FormatSource().format(dir);
    }

    /**
     * Format a Java source file. Files without ".java" extension are ignored.
     *
     * @param src
     *            Source file.
     */
    private void format(final File src) {
        if (src.isFile()) {
            if (Strings.getExtension(src).equals(".java")) {
                UtilIO.saveStringToFile(format(UtilIO.loadFile(src)), src);
            }
        } else if (src.isDirectory()) {
            for (File f : src.listFiles()) {
                format(f);
            }
        }
    }

    /**
     * Search string, split on two lines to ensure that it is not replaced when
     * FormatSource changes the real one.
     */
    private static final String APP_VERSION_ASSIGNMENT = "public static "
            + "final long UNI_VERSION =";

    /**
     * Pre-compile time replacement line.
     */
    private static final String REPLACEMENT_LINE = "    "
            + APP_VERSION_ASSIGNMENT + " "
            + Version.getInstance().createNewVersion() + "L;";

    /**
     * Format a vector of Java source lines.
     *
     * @param src
     *            Source file stored in a List &lt; String &gt;.
     * @return Formatted Java source.
     */
    private static String format(final List<String> src) {
        StringWriter sw = new StringWriter();
        int i = 0;
        int lastPopulated = -1;
        for (String s : src) {
            if (!s.trim().equals("")) {
                lastPopulated = i;
            }
            i++;
        }
        i = 0;
        FOR: for (String s : src) {
            if (i <= lastPopulated) {
                if (s.indexOf(APP_VERSION_ASSIGNMENT) > -1) {
                    s = REPLACEMENT_LINE;
                }
                sw.append(trimEnd(s).replace("\u0009", "    ").replace(
                        "{\u0020", "{").replace("{" + "}", "{\u0020}"));
                sw.append(Strings.getEol());
            } else {
                break FOR;
            }
            i++;
        }
        return sw.toString();
    }

    /**
     * For unit testing, format an array of Strings.
     *
     * @param src
     *            String[] array of source lines.
     * @return Formatted java source.
     */
    public static String format(final String[] src) {
        List<String> v = new ArrayList<String>();
        for (String s : src) {
            v.add(s);
        }
        return format(v);
    }

    /**
     *
     * Trim the spaces off the end of a String.

     * <h5>References</h5>
     * <ul>
     * <li>
     * <a
     * href="http://java-interview-faqs.blogspot.com/2009/07/playing-with-java-
     * string-trim-basics.html
     * ">java-interview-faqs.blogspot.com/2009/07/playing-with-java-
     * string-trim-basics.html</a>.
     * </li></ul>
     *
     * @param str
     *            String.
     * @return String s without any trailing spaces.
     */
    public static String trimEnd(final String str) {
        String s = str;
        if (s == null) {
            return "";
        }
        while (s.endsWith(" ")) {
            s = s.substring(0, s.length() - 1);
        }
        return s;
    }
}
